using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Cz.CzFileConfig
{
    public class CzFileConfig : ICzConfig
    {
        [CzServiceField(canNull = true)] private ICzLog log;
        [CzServiceField] private ICzJson json;
        
        private readonly string filePath;
        private Dictionary<string, object> configData;
        private Dictionary<string, object> tempConfigData;
        
        private DateTime fileLastWriteTime;
        private int flushTime;    // 刷新频率(秒)
        private bool debug;
        private CancellationTokenSource flushTaskToken;
        
        public CzFileConfig()
        {
            this.filePath = AppDomain.CurrentDomain.BaseDirectory + "config_template.json";
        }
        
        public CzFileConfig(string filePath)
        {
            this.filePath = filePath;
        }
        
        public void Start()
        {
            Console.WriteLine($"CzFileConfig start 配置文件:{filePath}");
            Assembly.GetExecutingAssembly().CreateFile(filePath, "CzConfig.config_template.json");
            Flush();
            if (flushTime > 0)
            {
                flushTaskToken = FlushTask();
            }
        }

        public event CzEvent OnChange;

        public T Get<T>(params string[] keys)
        {
            var value = GetValFromDict(tempConfigData, keys) ?? GetValFromDict(configData, keys);

            if (value == null)
                throw new Exception("缺少配置信息:" + keys.Join("->"));

            if (value is T ret)
                return ret;
            
            return (T)Convert.ChangeType(value, typeof(T));
        }

        public T GetCanNull<T>(params string[] keys)
        {
            return GetWithDefault<T>(default, keys);
        }

        public T GetWithDefault<T>(T def, params string[] keys)
        {
            var value = GetValFromDict(tempConfigData, keys) ?? GetValFromDict(configData, keys);

            if (value == null)
                return def;
            
            if (value is T ret)
                return ret;
            
            return (T)Convert.ChangeType(value, typeof(T));
        }

        public void Set<T>(T val, params string[] keys)
        {
            var dict = tempConfigData;
            for (int i = 0; i < keys.Length - 1; i++)
            {
                if (!dict.TryGetValue(keys[i], out var _dict))
                {
                    _dict = new Dictionary<string, object>();
                    dict[keys[i]] = _dict;
                }

                dict = (Dictionary<string, object>)_dict;
            }

            dict[keys.Last()] = val;
            OnChange?.Invoke();
        }

        public void ReSet()
        {
            tempConfigData.Clear();
            OnChange?.Invoke();
        }
        
        object GetValFromDict(Dictionary<string, object> dict, params string[] keys)
        {
            object value = null;
            foreach (var key in keys)
            {
                if (dict == null || !dict.TryGetValue(key, out value))
                    return null;

                if (value is Dictionary<string, object> _dictionary)
                    dict = _dictionary;
                else
                    dict = null;
            }

            return value;
        }
        
        void Flush()
        {
            string jsonStr;
            using (var sr = new StreamReader(File.OpenRead(filePath)))
            {
                jsonStr = sr.ReadToEnd();
            }

            configData = json.Deserialize<Dictionary<string, object>>(jsonStr);
            flushTime = GetWithDefault(-1, "config", "flush_time");
            debug = GetWithDefault(false, "config", "debug");
            
            if(debug)
                Console.WriteLine("配置信息:" + configData.ToJson() + "\n");
        }
        
        CancellationTokenSource FlushTask()
        {
            var token = new CancellationTokenSource();
            Task.Run(() =>
            {
                while (!token.IsCancellationRequested)
                {
                    Task.Delay(flushTime * 1000, token.Token).Wait(token.Token);
                    var fileInfo = new FileInfo(filePath);
                    if (fileLastWriteTime != fileInfo.LastWriteTime)
                    {
                        fileLastWriteTime = fileInfo.LastWriteTime;
                        Flush();
                        OnChange?.Invoke();
                        if (debug)
                            log.Info("Flush Config", configData.ToJson());
                    }

                    if(flushTime < 0)
                        break;
                }
            }, token.Token);
            return token;
        }
    }
}